#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>

// Shader程序代码模型
struct ShaderProgramSource {
    std::string VertexSource;
    std::string FragementSource;
};

// 封装函数从文件中读取shader, shader文件预处理函数
static ShaderProgramSource ParseShaderFile(const std::string& filePath) {
    enum class ShaderType {
        NONE = -1,
        VERTEX = 0,
        FRAGMENT = 1,
    };

    // 读取文件内容到line指针
    std::ifstream stream(filePath);

    std::string line;
    std::stringstream ss[2]; // 用于装载shader文件预处理内容模型
    ShaderType type = ShaderType::NONE;
    while (getline(stream, line)) {
        if (line.find("#shader") != std::string::npos) {
            if (line.find("vertex") != std::string::npos) { type = ShaderType::VERTEX; }
            else if (line.find("fragement") != std::string::npos) { type = ShaderType::FRAGMENT; }
        }

        // ! "#shader"
        else {
            ss[(int)type] << line << '\n';
        }
    }
    return { ss[0].str(), ss[1].str() };
}

// 编译shader程序
static unsigned int CompileShader(unsigned int type, const std::string& source) {
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str();
    glShaderSource(id, 1, &src, nullptr); // https://docs.gl/gl4/glShaderSource
    glCompileShader(id);

    // TODO: Error Handling
    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        char* message = (char*)alloca(length * sizeof(char)); // 在堆栈中申请一块内存用于从gl获取错误的编译信息
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to copmpile" << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

// 创建shader程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);

    // 对于shader是否需要清理
    // 1. shader程序体量很小, 不是很需要清理
    // 2. 如果删除了显卡的着色器程序, 那么着色器程序将无法进行调试
    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main(void) {

    // 创建GLFW窗体
    GLFWwindow* window;
    if (!glfwInit()) return -1; // 初始化GLFW
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL); // 初始化一个窗口并且获取它的OpenGL上下文
    if (!window) {
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window); // Make the window's context current

    // 初始化GLEW
    // GLEW是一个跨平台的OpenGL封装库, 用于整合多平台OpenGLAPI的使用
    // 根据文档所述GLEW初始化必须在创建完一个OpenGL上下文后进行
    // https://glew.sourceforge.net/basic.html -> Initializing GLEW
    // GLEW初始化阶段需要在C++预处理器中传入 Processor Definitions -> "GLEW_STATIC"
    GLenum err = glewInit();
    if (GLEW_OK != err) { std::cout << err << std::endl; }
    std::cout << glGetString(GL_VERSION) << std::endl; // 通过GLEW调用OpenGLAPI实现跨平台的目的

    // (1) 创建/设置顶点缓冲区 VRAM
    // 创建顶点索引缓冲区
    unsigned int indcies[] = { 0, 1, 2, 2, 3, 0 };
    unsigned int ibo; // IndexBufferObject
    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); // 使用API给状态机绑定当前操作的buffer
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indcies, GL_STATIC_DRAW); // 给buffer添加数据

    // 创建顶点的位置属性的缓冲区
    float vertexPosisitons[] = {
        -0.5f, -0.5f, // 0
         0.5f, -0.5f, // 1
         0.5f, 0.5f,  // 2
        -0.5f, 0.5f,  // 3
    };
    unsigned int vertexBuffer; // 定义一个无符号整形buffer
    glGenBuffers(1, &vertexBuffer); // 使用API创建OpenGLBuffer
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); // 使用API给状态机绑定当前操作的buffer
    glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), vertexPosisitons, GL_STATIC_DRAW); // 给buffer添加数据

    // (2) 对于顶点的属性进行指定缓冲区布局分配 setting layout of buffers
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
    // glVertexAttribPointer API 参数详细解释
    // index 属性顺序
    // size 大小
    // type 类型
    // if_normalize 是否01标准化
    // stride 该顶点attributes占用buffer大小
    // float 偏移

    // (3) 编写/链接/编译 显卡着色器程序
    ShaderProgramSource source = ParseShaderFile("resources/shaders/Basic.shader"); // properties -> DEBUG -> Working Directory VisualStudio默认不是main函数位置, 而是项目位置
    // 打印输出一下通过函数预处理之后的文件内容
    std::cout << "Vertex Shader Source: " << std::endl;
    std::cout << source.VertexSource << std::endl;
    std::cout << std::endl;
    std::cout << "Fragment Shader Source: " << std::endl;
    std::cout << source.FragementSource << std::endl;
    unsigned int shader = CreateShader(source.VertexSource, source.FragementSource);
    glUseProgram(shader); // 调用API使用程序

    // 重复渲染循环直到关闭了窗口 Render here
    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT); // Clear buffers

        // (4) 执行DrawCall, CPU通知GPU使用着色器程序绘制
        // https://docs.gl/gl4/glDrawElements
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

        glfwSwapBuffers(window); // Swap front and back buffers
        glfwPollEvents(); // Poll for and process events
    }

    // (5) 在窗口关闭后从显卡分配内存中删除shader源码
    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}